package com.jf.net.utils

import okhttp3.internal.EMPTY_BYTE_ARRAY
import okhttp3.internal.and
import java.io.File
import java.io.FileInputStream
import java.io.IOException
import java.security.DigestInputStream
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
import java.util.Locale

/**
 * MD5Util
 * @author JackyFlame
 * @time 2023/10/20 17:07
 * @description
 */
object MD5Util {
    private val HEX_DIGITS_UPPER =
        charArrayOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F')
    private val HEX_DIGITS_LOWER =
        charArrayOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f')

    /**
     * 计算String的md5
     * @param input 待处理字符串
     * @param salt 盐值
     * @return [String]
     */
    fun string2String(input: String, salt: String = ""): String {
        if (input.isEmpty()) return ""
        return byteArray2HexString(hashByteArray((input + salt).encodeToByteArray(), "MD5"))
    }

    /**
     * 计算ByteArray的md5
     * @param data 待处理ByteArray
     * @param salt 盐值
     * @return [String]
     */
    fun byteArray2String(data: ByteArray, salt: ByteArray): String {
        if (data.isEmpty()) return ""
        val dataSalt = ByteArray(data.size + salt.size)
        System.arraycopy(data, 0, dataSalt, 0, data.size)
        System.arraycopy(salt, 0, dataSalt, data.size, salt.size)
        return byteArray2HexString(hashByteArray(dataSalt, "MD5"))
    }

    /**
     * 计算File的md5
     * @param file 待计算文件
     * @return [String]
     */
    fun file2String(file: File): String {
        return byteArray2HexString(file2ByteArray(file))
    }

    /**
     * 计算File的md5
     * @param filePath 待计算文件的路径
     * @return [String]
     */
    fun file2String(filePath: String): String {
        return if (filePath.isEmpty()) ""
        else file2String(File(filePath))
    }

    /**
     * 计算File的md5
     * @param filePath 待计算文件的路径
     * @return [ByteArray]
     */
    fun file2ByteArray(filePath: String): ByteArray {
        return if (filePath.isEmpty()) EMPTY_BYTE_ARRAY
        else file2ByteArray(File(filePath))
    }

    /**
     * 计算File的md5
     * @param file 待计算文件
     * @return [ByteArray]
     */
    fun file2ByteArray(file: File): ByteArray {
        var fis: FileInputStream? = null
        val digestInputStream: DigestInputStream
        return try {
            fis = FileInputStream(file)
            var md = MessageDigest.getInstance("MD5")
            digestInputStream = DigestInputStream(fis, md)
            val buffer = ByteArray(256 * 1024)
            while (true) {
                if (digestInputStream.read(buffer) <= 0) break
            }
            md = digestInputStream.messageDigest
            md.digest()
        } catch (e: NoSuchAlgorithmException) {
            e.printStackTrace()
            EMPTY_BYTE_ARRAY
        } catch (e: IOException) {
            e.printStackTrace()
            EMPTY_BYTE_ARRAY
        } finally {
            try {
                fis?.close()
            } catch (e: IOException) {
                e.printStackTrace()
            }
        }
    }


    /**
     * 返回hash后的ByteArray
     * @param data 待处理ByteArray
     * @param algorithm 算法名
     */
    fun hashByteArray(data: ByteArray, algorithm: String): ByteArray {
        if (data.isEmpty()) return EMPTY_BYTE_ARRAY
        return with(MessageDigest.getInstance(algorithm)) {
            update(data)
            digest()
        }
    }

    /**
     * ByteArray转成十六进制字符串
     * @param input 待处理ByteArray
     * @param isUpperCase 生成的字符串是否是大写
     */
    fun byteArray2HexString(input: ByteArray, isUpperCase: Boolean = false): String {
        if (input.isEmpty()) return ""
        val hexDigits = if (isUpperCase) HEX_DIGITS_UPPER else HEX_DIGITS_LOWER
        val ret = CharArray(input.size shl 1)
        var k = 0
        input.forEach {
            ret[k++] = hexDigits[it.toInt() shr 4 and 0x0f]
            ret[k++] = hexDigits[it and 0x0f]
        }
        return String(ret)
    }

    /**
     * 十六进制字符串转成ByteArray
     * @param hexStr 待处理字符串
     */
    @ExperimentalStdlibApi
    fun hexString2ByteArray(hexStr: String): ByteArray {
        if (hexStr.isEmpty()) return EMPTY_BYTE_ARRAY
        var hexString = hexStr
        var len = hexString.length
        if (len % 2 != 0) {
            hexString = "0$hexString"
            len += 1
        }
        val hexBytes = hexString.uppercase(Locale.getDefault()).toCharArray()
        val ret = ByteArray(len shr 1)
        var i = 0
        while (i < len) {
            ret[i shr 1] = (hex2Dec(hexBytes[i]) shl 4 or hex2Dec(hexBytes[i + 1])).toByte()
            i += 2
        }
        return ret
    }

    private fun hex2Dec(hexChar: Char): Int {
        return when (hexChar) {
            in '0'..'9' -> {
                hexChar - '0'
            }
            in 'A'..'F' -> {
                hexChar - 'A' + 10
            }
            else -> {
                throw IllegalArgumentException()
            }
        }
    }
}